/**
 * @author Artur Klajnerok <arturk@wikia-inc.com>
 * @see http://github.com/Wikia/ponto
 */

package com.leqee.wxshop.webviewapi;

import android.content.Context;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;

import com.leqee.wxshop.utils.LogUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Ponto Android implementation.
 * Two way communication between WebViews and Native Code in clean standardized manner.
 * Provides callback mechanism to handle properly each request and response
 */
public class Ponto {

    public static final String TAG = "Ponto";

    /**
     * Package that contains classes which can be invoked by WebView JavaScript
     */
    private String mClassPackage;

    /**
     * WebView that will communicate with native code
     */
    private WebView mWebView;

    /**
     * Callbacks to get WebView response for a request from the native code
     */
    private Map<String, RequestCallback> mCallbacks;

    public Ponto(WebView webView, String classPackage) {
        mCallbacks = new HashMap<String, RequestCallback>();
        mClassPackage = classPackage + ".";
        mWebView = webView;
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.addJavascriptInterface(new PontoProtocol(), PontoProtocol.TAG);
    }

    /**
     * Makes a request to the WebView JavaScript
     *
     * @param className  The class name to instantiate.
     * @param methodName The method name to invoke.
     * @param params     The parameters that will be passed to invoked method.
     * @param callback   The callback for this request
     */
    public void invoke(String className, String methodName, String params, RequestCallback callback) {
        String callbackId = UUID.randomUUID().toString();

        if (callbackId != null && callback != null) {
            mCallbacks.put(callbackId, callback);
        }

        StringBuilder requestString = new StringBuilder();
        requestString.append("javascript:Ponto.request('{")
                .append("\"target\": \"").append(className).append("\", ")
                .append("\"method\": \"").append(methodName).append("\", ")
                .append("\"params\": ").append(params).append(", ")
                .append("\"callbackId\": \"").append(callbackId).append("\"")
                .append("}');");
        Log.d(TAG, "requestString = " + requestString);
        mWebView.loadUrl(requestString.toString());
    }

    /**
     * Communication protocol that should be used as JavascriptInterface
     */
    public class PontoProtocol {

        public static final String TAG = "PontoProtocol";

        /**
         * Represents a completed request
         */
        public static final int RESPONSE_COMPLETE = 0;

        /**
         * Represents a failed request with errors
         */
        public static final int RESPONSE_ERROR = 1;

        /**
         * The value of String that is null
         */
        private static final String NULL_STRING = "null";

        /**
         * The value of String that is undefined
         */
        private static final String UNDEFINED_STRING = "undefined";

        /**
         * Key for message response parameter
         */
        private static final String KEY_MESSAGE = "message";

        /**
         * Called by the web layer to perform a request on native layer
         *
         * @param execContext The JavaScript context.
         * @param className   The class name to instantiate.
         * @param methodName  The method name to invoke.
         * @param params      The parameters that will be passed to invoked method.
         * @param callbackId  The id of web callback.
         * @param async
         */
        @JavascriptInterface
        public void request(String execContext, String className, String methodName, String params,
                            String callbackId, String async) {
            try {
                Class<?> cls = Class.forName(mClassPackage + className);
                Object object = cls.newInstance();
                Method method = cls.getDeclaredMethod(methodName, this.getClass(), String.class, String.class, Context.class);
                method.invoke(object, this, callbackId, params, mWebView.getContext());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @JavascriptInterface
        public void response(String execContext, String callbackId, String params) {

            int responseType = getResponeTypeFromParams(params);
            if (callbackId != null && !callbackId.equalsIgnoreCase(UNDEFINED_STRING) &&
                    mCallbacks.containsKey(callbackId)) {

                RequestCallback callback = mCallbacks.get(callbackId);

                switch (responseType) {
                    case RESPONSE_COMPLETE:
                        callback.onSuccess();
                        break;
                    case RESPONSE_ERROR:
                    default:
                        callback.onError();
                        break;
                }
                mCallbacks.remove(callbackId);
            }
        }

        public void javascriptCallback(final String callbackId, final int type, final String params) {
            LogUtils.d("javascriptCallback type = " + type + ", params  =" + params);
            mWebView.post(new Runnable() {
                @Override
                public void run() {
                    StringBuilder responseString = new StringBuilder();
                    responseString.append("javascript:Ponto.response('{")
                            .append("\"type\": ").append(type).append(", ")
                            .append("\"params\": ").append(params).append(", ")
                            .append("\"callbackId\": \"").append(callbackId).append("\"")
                            .append("}');");
                    mWebView.loadUrl(responseString.toString());
                }
            });
        }

        private int getResponeTypeFromParams(String params) {
            JSONObject responseParams;
            int type = RESPONSE_COMPLETE;
            try {
                responseParams = new JSONObject(params);
                if (responseParams != null) {
                    type = responseParams.optInt("type");
                }
            } catch (JSONException e) {
                type = RESPONSE_ERROR;
                Log.e(TAG, "JSONException while parsing messaging data", e);
            }
            return type;
        }

        public void javascriptCallbackSucc(final String callbackId, final String params) {
        }

        public void javascriptCallbackFailed(final String callbackId, final String params) {
        }
    }

    /**
     * Interface definition for a callbacks that are invoked
     * when WebView responds for a request from the native code.
     */
    public interface RequestCallback {

        /**
         * Callback method to be invoked when request was successful
         */
        void onSuccess();

        /**
         * Callback method to be invoked when request failed
         */
        void onError();
    }
}